/*
The MIT License

Copyright (c) 2011 Sean M. Thomas

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "deferredRenderer.h"
#include <stdio.h>

#include <gtx/rotate_vector.hpp>
#include "Camera.h"


void setDeferredRenderer(fs_first_pass_draw_options settings) {
	glEnable(GL_DEPTH_TEST);
	glActiveTexture(GL_TEXTURE0);
}

void changeGBufferDisplayType(fs_shader_container &gBufferDisplay, int displayType) {
	glUseProgram(gBufferDisplay.shader);
	glUniform1i(gBufferDisplay.uniforms[U_GBUFFER_VIEW], (GLint)displayType);
}

void checkFramebufferStatus(GLenum framebufferStatus) {
	switch (framebufferStatus) {
        case GL_FRAMEBUFFER_COMPLETE_EXT: break;
        case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:
            printf("Attachment Point Unconnected");
            break;
        case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:
            printf("Missing Attachment");
            break;
        case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:
            printf("Dimensions do not match");
            break;
        case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:
            printf("Formats");
            break;
        case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:
            printf("Draw Buffer");
            break;
        case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:
            printf("Read Buffer");
            break;
        case GL_FRAMEBUFFER_UNSUPPORTED_EXT:
            printf("Unsupported Framebuffer Configuration");
            break;
        default:
            printf("Unkown Framebuffer Object Failure");
            break;
    }
}
const float rotationAngles[] = {.0f, 45.0f, 90.0f, 135.0f, 180.0f, 225.0f, 270.0f, 315.0f};

float clip(float val) {
	if(val < .0f)
		return .0f;
	else if(val > 1.0f)
		return 1.0f;
	else
		return val;
}

int max(float a, int b) {
	return int(a > b ? a : b);
}

int min(float a, int b) {
	return int(a < b ? a : b);
}

// Point light version
// Using method described at: http://www.gamasutra.com/view/feature/2942/the_mechanics_of_robust_stencil_.php?page=6
void updateScissorRect(ds_point_light* point, mat4 *view, mat4 *persp, mat3 *invTrans, int width, int height, Camera *cam) {
	
	vec3 camDir = cam->getDirection();
	vec3 pos = vec3(*view * vec4(point->position, 1.0f));

	float dist = dot(camDir, point->position - cam->getPosition());
	// Fast reject if light is far behind
	if(-dist > point->cutoff_far) {
		glScissor(0,0,0,0);
		return;
	}

	float r = point->cutoff_far;

	int xMin = 0, xMax = width, yMin = 0, yMax = height;

	vec3 pos2 = pos * pos;
	float r2 = r*r;

	float d = (r2*pos2.x - (pos2.x + pos2.z)*(r2 - pos2.z));
	if(d > 0) {
		float nx1, nx2, nz1, nz2;
		float lx2Pluslz2 = pos2.x + pos2.z;
		float rLx = r*pos.x;
		float sqrtD = float(sqrt(d));

		nx1 = (rLx + sqrtD)/lx2Pluslz2;
		nx2 = (rLx - sqrtD)/lx2Pluslz2;
		nz1 = (r - nx1*pos.x)/pos.z;
		nz2 = (r - nx2*pos.x)/pos.z;

		if(nx1 == 0 || nx2 == 0 || nz1 == 0 || nz2 == 0) {
			glScissor(0,0,width,height);
			return;
		}

		// < 0 corresponds to points in front?
		float xp;
		vec4 p;
		if(pos.z - r*nz1 < 0) {
			p = *persp * vec4(pos.x - r*nx1, 0, pos.z - r*nz1, 1.0f);
			p /= p.w;
			xp = (.5f*p.x + .5f)*width;
			if(-r*nx1 < 0)
				xMin = max(xp, xMin);
			else
				xMax = min(xp, xMax);
		}

		if(pos.z - r*nz2 < 0) {
			p = *persp * vec4(pos.x - r*nx2, 0, pos.z - r*nz2, 1.0f);
			p /= p.w;
			xp = (.5f*p.x + .5f)*width;
			if(-r*nx2 < 0)
				xMin = max(xp, xMin);
			else
				xMax = min(xp, xMax);
		}
	}

	d = (r2*pos2.y - (pos2.y + pos2.z)*(r2 - pos2.z));
	if(d > 0) {
		float ny1, ny2, nz1, nz2;
		float ly2Pluslz2 = pos2.y + pos2.z;
		float rLy = r*pos.y;
		float sqrtD = float(sqrt(d));

		ny1 = (rLy + sqrtD)/ly2Pluslz2;
		ny2 = (rLy - sqrtD)/ly2Pluslz2;
		nz1 = (r - ny1*pos.y)/pos.z;
		nz2 = (r - ny2*pos.y)/pos.z;

		if(ny1 == 0 || ny2 == 0 || nz1 == 0 || nz2 == 0) {
			glScissor(0,0,width,height);
			return;
		}

		// < 0 corresponds to points in front?
		float yp;
		vec4 p;
		if(pos.z - r*ny1 < 0) {
			p = *persp * vec4(0, pos.y - r*ny1, pos.z - r*nz1, 1.0f);
			p /= p.w;
			yp = (.5f*p.y + .5f)*height;
			if(-r*ny1 < 0)
				yMin = max(yp, yMin);
			else
				yMax = min(yp, yMax);
		}

		if(pos.z - r*nz2 < 0) {
			p = *persp * vec4(0, pos.y - r*ny2, pos.z - r*nz2, 1.0f);
			p /= p.w;
			yp = (.5f*p.y + .5f)*height;
			if(-r*ny2 < 0)
				yMin = max(yp, yMin);
			else
				yMax = min(yp, yMax);
		}
	}

	glScissor(xMin, yMin, xMax - xMin, yMax - yMin);
}


// Spot light version
// Completely broken
void updateScissorRect(ds_spot_light* spot, mat4 *view, mat4 *persp, mat3 *invTrans, int width, int height) {
	return;
	
	// Need to transform to view space first
	vec3 vsDir = normalize((*invTrans) * spot->direction);
	vec3 vsPos = vec3((*view) * vec4(spot->position, 1.0f));

	// going to overestimate
	// Trace a ray along direction to max distance
	vec3 maxStraight = vsPos + vsDir*spot->cutoff_far;
	float endRadius = (float)sqrt(spot->cutoff_far*spot->cutoff_far*(1/(spot->half_angle_cos*spot->half_angle_cos) - 1));
	vec4 endPoint;
	vec3 startDirection = (abs(vsDir.x) < abs(vsDir.y) ? normalize(cross(vsDir,vec3(1.0f, .0f, .0f))) : normalize(cross(vsDir,vec3(.0f, 1.0f, .0f))));
	vec4 vsPosPersp = (*persp)*vec4(vsPos,1.0f);
	vsPosPersp /= vsPosPersp.w;
	vec3 mins(vsPosPersp), maxes(vsPosPersp);
	vec2 screenCoords = .5f*vec2(vsPosPersp.x, vsPosPersp.y) + .5f;

	// These comparisons might actually need to happen in view space instead.
	vec3 rotateV;
	for(int i = 0; i < 8; ++i) {
		rotateV = gtx::rotate_vector::rotate(startDirection, rotationAngles[i], vsDir);
		endPoint = vec4(maxStraight, 1.0f) + vec4(rotateV, .0f)*endRadius;
		endPoint = (*persp) * endPoint;
		endPoint /= endPoint.w;
		screenCoords = .5f*vec2(endPoint.x, endPoint.y) + .5f;


		//if(endPoint.z > 1)
		//	continue;

		if(endPoint.x < mins.x)
			mins.x = endPoint.x;
		if(endPoint.y < mins.y)
			mins.y = endPoint.y;
		if(endPoint.x > maxes.x)
			maxes.x = endPoint.x;
		if(endPoint.y > maxes.y)
			maxes.y = endPoint.y;
	}

	//maxes.z = mins.z;

	// Translate the points to screen
	mins = .5f*mins + .5f;
	maxes = .5f*maxes + .5f;
	maxes.x = clip(maxes.x);
	maxes.y = clip(maxes.y);
	mins.x = clip(mins.x);
	mins.y = clip(mins.y);

	glScissor(int(width*mins.x), int(height*mins.y), int(width*(maxes.x - mins.x)), int(height*(maxes.y - mins.y)));
}	